package com.unibeta.cloudtest.mesher.service.impl;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.unibeta.cloudtest.mesher.auth.context.AuthContext;
import com.unibeta.cloudtest.mesher.auth.context.AuthContextHolder;
import com.unibeta.cloudtest.mesher.auth.service.AuthorizationService;
import com.unibeta.cloudtest.mesher.dto.PasswordChange;
import com.unibeta.cloudtest.mesher.modules.upm.entity.Tenant;
import com.unibeta.cloudtest.mesher.modules.upm.entity.User;
import com.unibeta.cloudtest.mesher.modules.upm.entity.UserLoginHistory;
import com.unibeta.cloudtest.mesher.modules.upm.repo.TenantRepo;
import com.unibeta.cloudtest.mesher.modules.upm.repo.UserLoginHistoryRepo;
import com.unibeta.cloudtest.mesher.modules.upm.repo.UserRepo;
import com.unibeta.cloudtest.mesher.service.EditorService;
import com.unibeta.cloudtest.mesher.service.UpmService;
import com.unibeta.cloudtest.openapi.OpenAPIHub;
import com.unibeta.cloudtest.openapi.OpenAPIHub.Response;
import com.unibeta.vrules.utils.CommonUtils;

import bsh.StringUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.signers.JWTSignerUtil;

@Service
public class UpmServiceImpl implements UpmService {

	private static final String IP_QUERY_SERVICE_126_NET = "http://ip.ws.126.net/ipquery?ip=";
	@Autowired
	TenantRepo tenantRepo;
	@Autowired
	UserRepo userRepo;
	@Autowired
	UserLoginHistoryRepo userLoginHistoryRepo;
	@Autowired
	EditorService editorService;

	@Override
	public User registerUser(User user) throws Exception {
		User u = this.getUserByName(user.getUsername());
		Assert.isTrue(u == null, "username '" + user.getUsername() + "' is exist");

		user.setPassword(MD5.create().digestHex(user.getPassword()));

		editorService.restoreWorkspace(user.getUsername(), "default");

		return userRepo.save(user);
	}

	@Override
	public boolean updateUser(User user) throws Exception {

		userRepo.save(user);
		return true;
	}

	@Override
	public String login(User user) throws Exception {
		User u = this.getUserByName(user.getUsername());
		Assert.isTrue(u != null, "username '" + user.getUsername() + "' is not exist");

		String digestHex = MD5.create().digestHex(user.getPassword());

		boolean checkPass = u.getPassword().equals(digestHex);
		Assert.isTrue(checkPass, "password is incorrect");

		u.setLastLoginTime(new Date());
		u.setLastLoginIp(user.getLastLoginIp());
		userRepo.save(u);

		user.setId(u.getId());

		CompletableFuture.runAsync(() -> {
			saveLoginData(user);
		});

		return this.authUserToken(u);
	}

	private void saveLoginData(User user) {
		UserLoginHistory userLogin = new UserLoginHistory();
		try {
			Response r = OpenAPIHub.call("get", IP_QUERY_SERVICE_126_NET + user.getLastLoginIp(), null, new HashMap());

			if (r != null && r.getStatus() == 200 && r.getBody() != null) {
				String[] ipInfos = r.getBody().toString().split("localAddress=");

				if (ipInfos.length >= 2) {
					ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
					engine.put("localAddress", null);
					String script = ipInfos[1].replace("\"", "'");

					Object value = engine.eval("localAddress = " + script);
					Bindings map = (Bindings) engine.get("localAddress");

					userLogin.setCity(map.get("city").toString());
					userLogin.setProvince(map.get("province").toString());
					userLogin.setCountry("中国");
				}
			} else {
				userLogin.setCity(r.getMessage());
				userLogin.setProvince(r.getMessage());
				userLogin.setCountry(r.getMessage());
			}
		} catch (Exception e) {

			userLogin.setCity("未知");
			userLogin.setProvince("未知");
			userLogin.setCountry("未知");

			e.printStackTrace();
		}

		userLogin.setUserId(user.getId());
		userLogin.setIp(user.getLastLoginIp());
		userLogin.setUsername(user.getUsername());

		userLoginHistoryRepo.save(userLogin);
	}

	private String authUserToken(User user) {

		Date date = new Date();

		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.add(Calendar.SECOND, AuthorizationService.TOKEN_EXPIRES_IN_SECONDS_AS_ONE_DAY);

		String secret = user.getPassword();

		String token = JWT.create().setAudience(new String[] { user.getUsername() }).setIssuedAt(new Date())
				.setExpiresAt(calendar.getTime()).setSigner(JWTSignerUtil.hs256(secret.getBytes())).sign();

		AuthContext context = AuthContextHolder.getContext();
		context.setCurrentUserDetails(user);

		Map session = new HashMap();

		context.setCurrentSession(session);
		AuthContextHolder.setContext(context);

		return token;
	}

	@Override
	public User getUserByName(String name) throws Exception {
		return userRepo.getUserByUsername(name);
	}

	@Override
	public User getUserById(Long id) throws Exception {
		return userRepo.getOne(id);
	}

	@Override
	public List<Tenant> getTenantList(Long userId) throws Exception {
		return tenantRepo.getTenantListByUserId(userId);
	}

	@Override
	public Tenant addTenant(Tenant tenant) throws Exception {
		return tenantRepo.save(tenant);
	}

	@Override
	public Boolean changePassword(PasswordChange passwordChange) throws Exception {
		User user = this.getUserByName(passwordChange.getUsername());

		String digestHex = MD5.create().digestHex(passwordChange.getOldPassword());

		boolean checkPass = user.getPassword().equals(digestHex);
		Assert.isTrue(checkPass, "old password is incorrect");
		Assert.isTrue(!CommonUtils.isNullOrEmpty(passwordChange.getNewPassword()), "new plassword is mandatory.");
		Assert.isTrue(!CommonUtils.isNullOrEmpty(passwordChange.getConfirmPassword()), "confirmed plassword is mandatory.");

		Assert.isTrue(passwordChange.getNewPassword().equals(passwordChange.getConfirmPassword()),
				"twice password inputs are inconsistent, please double check.");
		
		user.setPassword(MD5.create().digestHex(passwordChange.getNewPassword()));
		
		this.updateUser(user);

		return false;
	}

}
